// Package testing provides testing utilities and helpers
package testing

import (
	"context"
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"
	"runtime"
	"strings"
	"testing"
	"time"

	"github.com/smart-snapshotter/internal/logging"
	"github.com/smart-snapshotter/pkg/types"
)

// TestSuite provides a base test suite with common functionality
type TestSuite struct {
	T           *testing.T
	Ctx         context.Context
	TempDir     string
	Logger      logging.Logger
	CleanupFuncs []func() error
}

// NewTestSuite creates a new test suite
func NewTestSuite(t *testing.T) *TestSuite {
	// Create context with timeout
	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
	t.Cleanup(func() {
		cancel()
	})

	// Create temp directory
	tempDir, err := ioutil.TempDir("", "smart-snapshotter-test-*")
	if err != nil {
		t.Fatalf("Failed to create temp directory: %v", err)
	}

	// Initialize logger for testing
	logger, err := logging.NewLogger(&types.LoggingConfig{
		Level:  "debug",
		Format: "text",
		Output: "stdout",
	})
	if err != nil {
		t.Fatalf("Failed to create test logger: %v", err)
	}

	suite := &TestSuite{
		T:           t,
		Ctx:         ctx,
		TempDir:     tempDir,
		Logger:      logger,
		CleanupFuncs: make([]func() error, 0),
	}

	// Add cleanup function to remove temp directory
	t.Cleanup(func() {
		suite.Cleanup()
	})

	return suite
}

// Cleanup performs cleanup operations
func (s *TestSuite) Cleanup() {
	// Execute cleanup functions in reverse order
	for i := len(s.CleanupFuncs) - 1; i >= 0; i-- {
		if err := s.CleanupFuncs[i](); err != nil {
			s.T.Logf("Cleanup function %d failed: %v", i, err)
		}
	}

	// Remove temp directory
	if s.TempDir != "" {
		if err := os.RemoveAll(s.TempDir); err != nil {
			s.T.Logf("Failed to remove temp directory %s: %v", s.TempDir, err)
		}
	}
}

// AddCleanup adds a cleanup function
func (s *TestSuite) AddCleanup(f func() error) {
	s.CleanupFuncs = append(s.CleanupFuncs, f)
}

// RequireNoError asserts that an error is nil
func (s *TestSuite) RequireNoError(err error, msg string) {
	if err != nil {
		s.T.Fatalf("%s: %v", msg, err)
	}
}

// RequireTrue asserts that a condition is true
func (s *TestSuite) RequireTrue(condition bool, msg string) {
	if !condition {
		s.T.Fatalf("Expected true: %s", msg)
	}
}

// RequireFalse asserts that a condition is false
func (s *TestSuite) RequireFalse(condition bool, msg string) {
	if condition {
		s.T.Fatalf("Expected false: %s", msg)
	}
}

// RequireEqual asserts that two values are equal
func (s *TestSuite) RequireEqual(expected, actual interface{}, msg string) {
	if expected != actual {
		s.T.Fatalf("%s: expected %v, got %v", msg, expected, actual)
	}
}

// RequireNotEqual asserts that two values are not equal
func (s *TestSuite) RequireNotEqual(expected, actual interface{}, msg string) {
	if expected == actual {
		s.T.Fatalf("%s: expected not %v, got %v", msg, expected, actual)
	}
}

// RequireNotNil asserts that a value is not nil
func (s *TestSuite) RequireNotNil(value interface{}, msg string) {
	if value == nil {
		s.T.Fatalf("Expected non-nil: %s", msg)
	}
}

// RequireNil asserts that a value is nil
func (s *TestSuite) RequireNil(value interface{}, msg string) {
	if value != nil {
		s.T.Fatalf("Expected nil: %s, got %v", msg, value)
	}
}

// Eventually asserts that a condition becomes true within a timeout
func (s *TestSuite) Eventually(condition func() bool, timeout time.Duration, interval time.Duration, msg string) {
	deadline := time.Now().Add(timeout)
	for time.Now().Before(deadline) {
		if condition() {
			return
		}
		time.Sleep(interval)
	}
	s.T.Fatalf("Condition not met within timeout: %s", msg)
}

// Consistently asserts that a condition remains true for a duration
func (s *TestSuite) Consistently(condition func() bool, duration time.Duration, interval time.Duration, msg string) {
	deadline := time.Now().Add(duration)
	for time.Now().Before(deadline) {
		if !condition() {
			s.T.Fatalf("Condition not consistently true: %s", msg)
		}
		time.Sleep(interval)
	}
}

// CreateTempFile creates a temporary file with content
func (s *TestSuite) CreateTempFile(name, content string) string {
	filePath := filepath.Join(s.TempDir, name)
	if err := ioutil.WriteFile(filePath, []byte(content), 0644); err != nil {
		s.T.Fatalf("Failed to create temp file %s: %v", filePath, err)
	}
	return filePath
}

// CreateTempDir creates a temporary directory
func (s *TestSuite) CreateTempDir(name string) string {
	dirPath := filepath.Join(s.TempDir, name)
	if err := os.MkdirAll(dirPath, 0755); err != nil {
		s.T.Fatalf("Failed to create temp directory %s: %v", dirPath, err)
	}
	return dirPath
}

// ReadFile reads a file and returns its content
func (s *TestSuite) ReadFile(path string) string {
	content, err := ioutil.ReadFile(path)
	if err != nil {
		s.T.Fatalf("Failed to read file %s: %v", path, err)
	}
	return string(content)
}

// FileExists checks if a file exists
func (s *TestSuite) FileExists(path string) bool {
	_, err := os.Stat(path)
	return err == nil
}

// DirExists checks if a directory exists
func (s *TestSuite) DirExists(path string) bool {
	info, err := os.Stat(path)
	if err != nil {
		return false
	}
	return info.IsDir()
}

// Log logs a message with test context
func (s *TestSuite) Log(msg string, args ...interface{}) {
	formattedMsg := fmt.Sprintf(msg, args...)
	s.Logger.Info(formattedMsg,
		logging.StringField("test", s.T.Name()),
		logging.StringField("file", getCallerFile()),
	)
}

// Logf logs a formatted message with test context
func (s *TestSuite) Logf(format string, args ...interface{}) {
	s.Log(format, args...)
}

// getCallerFile returns the caller file information
func getCallerFile() string {
	_, file, line, ok := runtime.Caller(2)
	if !ok {
		return "unknown"
	}

	// Get relative path from project root
	if idx := strings.Index(file, "smart-snapshotter"); idx != -1 {
		file = file[idx+len("smart-snapshotter")+1:]
	}

	return fmt.Sprintf("%s:%d", file, line)
}